#include <kutils.h>
#include "memory.h"
#include "heap.h"
#include "paging.h"
#include "display.h"

extern address_space_t* active_space;
extern u32int mem_features;
extern u32int page_size;

heap_t *create_heap(u32int start, u32int end)
{
	//allocate a structure to describe our new heap
	heap_t *heap = (heap_t*)id_page_alloc(sizeof(heap_t));

	heap->start = start;
	heap->end = end;
	heap->min_chunck_size = 0x20;//32 bytes or 8 s32integers

#ifdef FLUIDIUM_DEBUG
	write_string("Memory: Creating kernel heap from "); write_hex(start);
	write_string(" to "); write_hex(end); write_string("\n");
#endif

	//map only the required pages for starting the heap. More pages will be mapped as needed.
	page_alloc(active_space, start, end);

	//zero all of the memory in the heap
	memset((u32int*)start, 0x00, (size_t)(end - start));
	
	//place the first header at the start of the heap
	heap->first_header = (heap_header_t*)start;
	heap->first_header->magic = HEAP_MAGIC;
	heap->first_header->magic1 = HEAP_MAGIC;
	heap->first_header->free = 1;
	heap->first_header->size = 0;//unknown size
	heap->first_header->next = 0;//there is no next header yet.

	mem_features |= MEM_FEATURES_HEAP;

	return heap;
}

u32int heap_alloc(heap_t *heap, u32int size)
{
	if(!(mem_features & MEM_FEATURES_HEAP))
		return 0;

	//enforce the minimum chunck size
	if(size < heap->min_chunck_size)
		size = heap->min_chunck_size;

	u32int iters = 0;

	//traverse the linked list of headers
	heap_header_t* header = heap->first_header;
	heap_header_t* header_dump = header;
	while(header)
	{
		if((header->magic == HEAP_MAGIC) & (header->magic1 == HEAP_MAGIC))
		{
			if(header->free)
			{
				if(!header->next)
				{//this is the last header
					u32int addr = (u32int)header;
 					addr += sizeof(heap_header_t);
					header->free = 0;
					header->size = size;

					u32int space_end = addr + size + sizeof(heap_header_t);
					if(space_end >= heap->end)
					{//heap expansion needed
						u32int old_end = heap->end;
						u32int new_end = space_end;
						new_end = (new_end & 0xFFFFF000) + page_size;

						if(!page_alloc(active_space, old_end, new_end))
							return 0;

						heap->end = new_end;
					}

					//setup the new header
					header->next = (heap_header_t*)(addr + size);
					header->next->magic = HEAP_MAGIC;
					header->next->magic1 = HEAP_MAGIC;
					header->next->free = 1;
					header->next->size = 0;
					header->next->next = 0;

					return addr;
				}//endif(!header->next)
	
				if(header->size >= size)
				{
					u32int addr = (u32int)header;
					addr += sizeof(heap_header_t);
					header->free = 0;
					memset((u32int*)addr,0,(size_t)size);	
					return addr;
				}				
			}//endif(header->free)

		if(!header->next)
			header_dump = header;		

		header = header->next;//advance to the next header
		}
		else
		{
#ifdef FLUIDIUM_DEBUG
			write_string("Memory: Heap Corruption!\n");
			write_hex((u32int)header);
			write_char(' ');
			write_hex(header->magic);
			write_char(' ');
			write_hex(header->magic1);
			write_char('\n');
#endif
			return 0;
		}
		iters++;
	}
#ifdef FLUIDIUM_DEBUG
	write_string("Memory: kmalloc could not find free space after ");
	write_number(iters);
	write_string(" search iterations.\n");
#endif
	return 0;
}

void heap_free(heap_t *heap, void *p)
{
	//write_string("kf");

	if(!(mem_features & MEM_FEATURES_HEAP))
		return;

	//sanity check
	if((u32int)p < heap->start)
	{
		write_string("Memory: Bad pos32inter passed to kfree() ");
		write_hex((u32int)p);
		write_string("\n");
		halt();
	}
	if((u32int)p > heap->end)
	{
		write_string("Memory: Bad pos32inter passed to kfree() ");
		write_hex((u32int)p);
		write_string("\n");
		halt();
	}

	//mark the header as free
	heap_header_t *f_header = (heap_header_t*)((u32int)p - sizeof(heap_header_t));
	if(f_header->magic == HEAP_MAGIC)
	{
		f_header->free = 1;
	}
	else
	{
		write_string("Memory: Bad pos32inter passed to kfree() ");
		write_hex((u32int)p);
		write_string("\n");
		halt();
	}
/*
	//Scan from the header that we just freed until we find 
	// either the end of the heap or a used chunck.
	heap_header_t *header = f_header;
	while(header)
	{
		if(header->magic == HEAP_MAGIC)
		{
			if(header->free)
			{
				if(header->size == 0)
				{//this is the last header

					write_string(" <");
					write_hex(heap->end);
					write_string(" to ");

					//if we find the end of the heap we mark the header that we just freed as the last one and
					//collapse the heap to the end of the header that we just freed.
					f_header->free = 1;					
					f_header->size = 0;
					f_header->next = 0;

					u32int old_end = heap->end;
					u32int new_end = ((u32int)f_header) + sizeof(heap_header_t);
					new_end = (new_end & 0xFFFFF000) + page_size;

					write_hex(new_end);

					page_free(&active_space, new_end + page_size, old_end);
					heap->end = new_end;

					write_string("> ");

					break;
				}
			}
			else
			{
				//if we find a used chunck nothing is done.
				write_string(" <cnc>");
				break;
			}
		header = header->next;//advance to the next header
		}
		else
		{
			write_string(" -- Heap Corruption! ");
			write_hex((u32int)header);
			write_char(' ');
			write_hex(header->magic);
			write_string(" -- ");
			halt();
		}
	}
//*/
}
